#pragma once
/** @addtogroup simcorefactoriesPolicies
 *
 *  @{
 */
#include <SimCoreFactory/ObjectFactory.h>

/*
Policy class to create nonlin solver object
*/
template <class CreationPolicy>
struct NonLinSolverOMCFactory : virtual public ObjectFactory<CreationPolicy>
{
public:
    NonLinSolverOMCFactory(PATH library_path, PATH modelicasystem_path, PATH config_path)
        : ObjectFactory<CreationPolicy>(library_path, modelicasystem_path, config_path)
          , _last_selected_solver("empty")
    {
        _non_linsolver_type_map = new type_map();
    }

    virtual ~NonLinSolverOMCFactory()
    {
        delete _non_linsolver_type_map;
        ObjectFactory<CreationPolicy>::_factory->UnloadAllLibs();
    }

    virtual shared_ptr<INonLinSolverSettings> createNonLinSolverSettings(string nonlin_solver)
    {
        string nonlin_solver_key;

        if (nonlin_solver.compare("newton") == 0)
        {
            fs::path newton_path = ObjectFactory<CreationPolicy>::_library_path;
            fs::path newton_name(NEWTON_LIB);
            newton_path /= newton_name;
            LOADERRESULT result = ObjectFactory<CreationPolicy>::_factory->LoadLibrary(
                newton_path.string(), *_non_linsolver_type_map);
            if (result != LOADER_SUCCESS)
            {
                throw ModelicaSimulationError(MODEL_FACTORY, "Failed loading Newton solver library!");
            }
            nonlin_solver_key.assign("extension_export_newton");
        }
        else if (nonlin_solver.compare("broyden") == 0)
        {
            fs::path broyden_path = ObjectFactory<CreationPolicy>::_library_path;
            fs::path broyden_name(BROYDEN_LIB);
            broyden_path /= broyden_name;
            LOADERRESULT result = ObjectFactory<CreationPolicy>::_factory->LoadLibrary(
                broyden_path.string(), *_non_linsolver_type_map);
            if (result != LOADER_SUCCESS)
            {
                throw ModelicaSimulationError(MODEL_FACTORY, "Failed loading Broyden solver library!");
            }
            nonlin_solver_key.assign("extension_export_broyden");
        }
        else if (nonlin_solver.compare("kinsol") == 0)
        {
            fs::path kinsol_path = ObjectFactory<CreationPolicy>::_library_path;
            fs::path kinsol_name(KINSOL_LIB);
            kinsol_path /= kinsol_name;
            LOADERRESULT result = ObjectFactory<CreationPolicy>::_factory->LoadLibrary(
                kinsol_path.string(), *_non_linsolver_type_map);
            if (result != LOADER_SUCCESS)
            {
                throw ModelicaSimulationError(MODEL_FACTORY, "Failed loading Kinsol solver library!");
            }
            nonlin_solver_key.assign("extension_export_kinsol");
        }
        else if (nonlin_solver.compare("hybrj") == 0)
        {
            fs::path hybrj_path = ObjectFactory<CreationPolicy>::_library_path;
            fs::path hybrj_name(HYBRJ_LIB);
            hybrj_path /= hybrj_name;
            LOADERRESULT result = ObjectFactory<CreationPolicy>::_factory->LoadLibrary(
                hybrj_path.string(), *_non_linsolver_type_map);
            if (result != LOADER_SUCCESS)
            {
                throw ModelicaSimulationError(MODEL_FACTORY, "Failed loading Hybrj solver library!");
            }
            nonlin_solver_key.assign("extension_export_hybrj");
        }
        else
            throw ModelicaSimulationError(MODEL_FACTORY, "Selected nonlinear solver is not available");
        _last_selected_solver = nonlin_solver;
        string nonlinsolversettings = nonlin_solver.append("Settings");
        std::map<std::string, factory<INonLinSolverSettings>>::iterator iter;
        std::map<std::string, factory<INonLinSolverSettings>>& nonLinSolversettingsfactory(
            _non_linsolver_type_map->get());
        iter = nonLinSolversettingsfactory.find(nonlinsolversettings);
        if (iter == nonLinSolversettingsfactory.end())
        {
            throw ModelicaSimulationError(MODEL_FACTORY, "No such nonlinear solver Settings");
        }
        shared_ptr<INonLinSolverSettings> nonlinsolversetting = shared_ptr<INonLinSolverSettings
        >(iter->second.create());
        return nonlinsolversetting;
    }

    virtual shared_ptr<INonLinearAlgLoopSolver> createNonLinSolver(string solver_name,
                                                                   shared_ptr<INonLinSolverSettings> solver_settings,
                                                                   shared_ptr<INonLinearAlgLoop> algLoop = shared_ptr<
                                                                       INonLinearAlgLoop>())
    {
        if (_last_selected_solver.compare(solver_name) == 0)
        {
            std::map<std::string, factory<INonLinearAlgLoopSolver, INonLinSolverSettings*, shared_ptr<INonLinearAlgLoop>
                     >>::iterator iter;
            std::map<std::string, factory<INonLinearAlgLoopSolver, INonLinSolverSettings*, shared_ptr<INonLinearAlgLoop>
                     >>& nonlinSolverFactory(_non_linsolver_type_map->get());
            iter = nonlinSolverFactory.find(solver_name);
            if (iter == nonlinSolverFactory.end())
            {
                throw ModelicaSimulationError(MODEL_FACTORY, "No such non linear Solver");
            }
            shared_ptr<INonLinearAlgLoopSolver> solver = shared_ptr<INonLinearAlgLoopSolver>(
                iter->second.create(solver_settings.get(), algLoop));
            return solver;
        }
        else
            throw ModelicaSimulationError(MODEL_FACTORY, "Selected nonlinear solver is not available");
    }

protected:
    string _last_selected_solver;
private:
    type_map* _non_linsolver_type_map;
};

/** @} */ // end of simcorefactoriesPolicies
